using System;
using System.Collections.Generic;
using System.Linq;

namespace DuplexTransformers
{
    // This class grew up too fast and too crazy.
    public static class TransformerFactory
    {
        private static readonly IDictionary<Type, object> Registry = new Dictionary<Type, object>();

        public static Transformer<TLeft, TRight> Get<TLeft, TRight>()
        {
            var leftType = typeof(TLeft);
            var rightType = typeof(TRight);

            var targetTransformerType = typeof(ITransformer<,>).MakeGenericType(new[] { leftType, rightType });

            if (Registry.ContainsKey(targetTransformerType))
            {
                return (Transformer<TLeft, TRight>)Registry[targetTransformerType];
            }
            return (Transformer<TLeft, TRight>)Construct(leftType, rightType);
        }

        public static Transformer<TLeft, TRight> Construct<TLeft, TRight>()
        {
            var rightType = typeof (TRight);
            var leftType = typeof (TLeft);
            return (Transformer<TLeft, TRight>) Construct(leftType, rightType);
        }

        private static object Construct(Type leftType, Type rightType)
        {
            var leftProperties = TypeInspector.GetProperties(leftType);
            var rightProperties = TypeInspector.GetProperties(rightType);

            var resultType = typeof (Transformer<,>).MakeGenericType(new[] {leftType, rightType});

            var result = Activator.CreateInstance(resultType);

            foreach (var leftProperty in leftProperties)
            {
                var rightProperty = rightProperties.Where(x => x.PrefixedName == leftProperty.PrefixedName).FirstOrDefault();
                if (rightProperty == null) continue;

                var leftPropertyType = leftProperty.PropertyType;
                var rightPropertyType = rightProperty.PropertyType;

                var propertyTransformer = GetRegisteredOrStandardOrConstructedTransformer(leftPropertyType, rightPropertyType);

                var propertyRelationshipType = typeof (PropertyRelationship<,,,>).MakeGenericType(new[] {leftType, rightType, leftPropertyType, rightPropertyType});

                var constructor = propertyRelationshipType.GetConstructors()[0];

                var propertyRelationship = constructor.Invoke(new[] {leftProperty.LambdaExpression, propertyTransformer, rightProperty.LambdaExpression});

                var method = result.GetType().GetMethod("AddRelationshipInternal");
                var parameters = new[] {propertyRelationship};
                method.Invoke(result, parameters);
            }

            var registeredType = typeof (ITransformer<,>).MakeGenericType(new[] {leftType, rightType});
            Registry[registeredType] = result;
            return result;
        }

        private static object GetRegisteredOrStandardOrConstructedTransformer(Type leftType, Type rightType)
        {
            System.Diagnostics.Debug.WriteLine("GetRegisteredOrStandardOrConstructedTransformer ");
            System.Diagnostics.Debug.WriteLine(leftType.Name);
            System.Diagnostics.Debug.WriteLine(rightType.Name);
            return GetRegisteredTransformerOrNull(leftType, rightType) ??
                   ConstructStandardTransformerOrNull(leftType, rightType) ??
                   Construct(leftType, rightType);
        }

        private static object GetRegisteredTransformerOrNull(Type leftType, Type rightType)
        {
            var targetTransformerType = typeof(ITransformer<,>).MakeGenericType(new[] { leftType, rightType });
            return Registry.ContainsKey(targetTransformerType) ? Registry[targetTransformerType] : null;
        }

        private static object ConstructStandardTransformerOrNull(Type leftType, Type rightType)
        {
            object result = null;
            
            if (leftType == rightType)
            {
                var propertyTransformerType = typeof (PassthruTransformer<>).MakeGenericType(new[] {leftType});
                result = Activator.CreateInstance(propertyTransformerType);
            }
            else if (leftType.IsGenericList() && rightType.IsGenericList())
            {
                if (leftType.GetListItemType() == rightType.GetListItemType())
                {
                    var itemTransformerType = typeof (PassthruTransformer<>).MakeGenericType(new[] {leftType.GetListItemType()});
                    var itemTransformer = Activator.CreateInstance(itemTransformerType);
                    var propertyTransformerType = typeof (ListTransformer<,>).MakeGenericType(new[] {leftType.GetListItemType(), rightType.GetListItemType()});
                    result = Activator.CreateInstance(propertyTransformerType, itemTransformer);
                }
                else
                {
                    //var itemTransformer = Construct(leftType.GetListItemType(), rightType.GetListItemType());
                    var itemTransformer = GetRegisteredOrStandardOrConstructedTransformer(leftType.GetListItemType(), rightType.GetListItemType());
                    var propertyTransformerType = typeof (ListTransformer<,>).MakeGenericType(new[] {leftType.GetListItemType(), rightType.GetListItemType()});
                    result = Activator.CreateInstance(propertyTransformerType, itemTransformer);
                }
            }
            return result;
        }
    }
}